<?php
/**
 * 登录相关
 * @author yupoxiong<i@yupoxiong.com>
 */

declare (strict_types=1);

namespace app\admin\service;

use app\admin\exception\AdminLoginLimitException;
use support\Response;
use think\facade\Cache;
use Webman\Event\Event;
use app\admin\model\AdminUser;
use app\admin\exception\AdminServiceException;

class AdminAuthService extends AdminBaseService
{
    /** @var int 记住登录cookie 1000天 */
    public const REMEMBER_COOKIE_MAX_AGE = 86400000;
    protected AdminUser $model;

    protected string $limitKeyPrefix = 'admin_login_count_';

    /**
     * @var string 保存登录用户信息cookie和session的[ID]key值
     */
    protected string $storeUidKey = 'admin_user_id';
    /**
     * @var string 保存登录用户信息cookie和session的[签名]key值
     */
    protected string $storeSignKey = 'admin_user_sign';
    /**
     * @var string 用来签名加密/解密的key
     */
    protected string $adminKey = '_ThisClassDefaultKey_';


    public function __construct()
    {
        $this->model = new AdminUser();

        $this->adminKey     = (string)(setting('admin.safe.admin_key') ?? $this->adminKey);
        $this->storeUidKey  = (string)(setting('admin.safe.store_uid_key') ?? $this->storeUidKey);
        $this->storeSignKey = (string)(setting('admin.safe.store_sign_key') ?? $this->storeSignKey);
    }

    /**
     * 用户登录
     * @param $username
     * @param $password
     * @return AdminUser
     * @throws AdminServiceException
     */
    public function login($username, $password): AdminUser
    {
        $admin_user = $this->model->where('username', '=', $username)->findOrEmpty();
        if ($admin_user->isEmpty()) {
            throw new AdminServiceException('用户不存在');
        }

        /** @var AdminUser $admin_user */
        $verify = password_verify($password, base64_decode($admin_user->password));
        if (!$verify) {
            throw new AdminServiceException('密码错误');
        }

        // 检查是否被冻结
        if ($admin_user->status !== 1) {
            throw new AdminServiceException('账号被冻结');
        }

        // Event_事件 管理用户登录
        Event::emit('admin_user.login', $admin_user);

        return $admin_user;
    }

    /**
     * 检测登录限制
     *
     * @throws AdminLoginLimitException
     */
    public function checkLoginLimit(): bool
    {
        $config   = setting('admin.login');
        $is_limit = (int)$config['login_limit'];
        if ($is_limit) {
            // 最大错误次数
            $max_count  = (int)$config['login_max_count'];
            $limit_hour = (int)$config['login_limit_hour'];
            // 缓存key
            $cache_key  = $this->limitKeyPrefix . md5((request())->getRealIp());
            $have_count = (int)Cache::get($cache_key);
            if ($have_count >= $max_count) {
                throw new AdminLoginLimitException('连续' . $max_count . '次登录失败，请' . $limit_hour . '小时后再试');
            }
            return true;
        }
        return true;
    }

    /**
     * 设置登录限制
     * @return bool
     */
    public function setLoginLimit(): bool
    {
        $is_limit = (int)setting('admin.login.login_limit');
        if ($is_limit) {
            // 最大错误次数
            $login_limit_hour = (int)setting('admin.login.login_limit_hour');
            // 缓存key
            $cache_key  = $this->limitKeyPrefix . md5((request())->getRealIp());
            $cache_time = $login_limit_hour * 3600;
            if (Cache::has($cache_key)) {
                Cache::set($cache_key, Cache::get($cache_key) + 1, $cache_time);
                return true;
            }
            Cache::set($cache_key, 1, $cache_time);
        }
        return true;
    }

    /**
     * 临时手动清除某个ip的限制
     * @param $ip
     * @return bool
     */
    public function clearLoginLimit($ip): bool
    {
        $cache_key = $this->limitKeyPrefix . md5($ip);
        return Cache::delete($cache_key);
    }


    /**
     * 获取登录用户信息
     * @return AdminUser
     * @throws AdminServiceException
     */
    public function getAdminUserAuthInfo(): AdminUser
    {
        //  当前管理员ID
        $admin_user_id = 0;
        // 当前获取登录信息的方式
        $store_from = 0;

        $session = (request())->session();

        if ($session->has($this->storeUidKey)) {
            // session
            $store_from    = 1;
            $admin_user_id = (int)$session->get($this->storeUidKey);
        } else if ((request())->cookie($this->storeUidKey)) {
            // cookie
            $store_from    = 2;
            $admin_user_id = (int)(request())->cookie($this->storeUidKey, 0);
        }

        if ($admin_user_id === 0) {
            throw new AdminServiceException('未找到登录信息');
        }

        $admin_user = $this->model
            ->where('id', '=', $admin_user_id)
            ->findOrEmpty();

        /** @var AdminUser $admin_user */
        if (!$admin_user) {
            throw new AdminServiceException('用户不存在');
        }

        if ($admin_user->status !== 1) {
            throw new AdminServiceException('用户被冻结');
        }

        // 如果是cookie中的用户，验证sign是否正确
        if ($store_from === 2) {
            $cookie_sign = (request())->cookie($this->storeSignKey);
            $check_sign  = $this->getUserSign($admin_user);
            if ($cookie_sign !== $check_sign) {
                throw new AdminServiceException('Cookie签名验证失败');
            }
            // 存在一下session
            $session->set($this->getStoreUidKey(), $admin_user->id);
        }

        return $admin_user;
    }


    /**
     * 设置用户登录信息
     * @param $admin_user
     * @param bool $remember
     * @param Response $response
     * @return Response
     */
    public function setAdminUserAuthInfo($admin_user, bool $remember, Response $response): Response
    {
        session([$this->getStoreUidKey() => $admin_user->id]);

        if ($remember) {
            $response->cookie($this->getStoreUidKey(), $admin_user->id, self::REMEMBER_COOKIE_MAX_AGE, '/');
            $response->cookie($this->getStoreSignKey(), $this->getUserSign($admin_user), self::REMEMBER_COOKIE_MAX_AGE, '/');
        }

        return $response;
    }


    /**
     * 退出
     * @param AdminUser $admin_user
     * @param Response $response
     * @return Response
     */
    public function logout(AdminUser $admin_user, Response $response): Response
    {
        // Event_事件 管理用户退出
        Event::emit('admin_user.logout', $admin_user);

        $session = (request())->session();
        $session->delete($this->getStoreUidKey());
        $response->cookie($this->getStoreUidKey(), '', 0, '/');
        $response->cookie($this->getStoreSignKey(), '', 0, '/');
        return $response;
    }

    /**
     * 获取签名
     * @param $admin_user
     * @return string
     */
    public function getUserSign($admin_user): string
    {
        return md5(md5($this->adminKey . $admin_user->id) . $this->adminKey);
    }

    /**
     * 在视图中检查指定url是否有权限
     * @param string $url url形式参考：user/edit，admin/user/edit，前缀"admin/"可以去掉
     * @return string
     * @throws AdminServiceException
     */
    public function viewCheckAuth(string $url): string
    {
        $user = $this->getAdminUserAuthInfo();

        $prefix = 'admin/';
        if (strpos(parse_name($url), $prefix) !== 0) {
            $url = $prefix . $url;
        }

        if (($user->id) === 1) {
            $result = '1';
        } else {
            $result = in_array(strtolower($url), array_map('strtolower', $user->auth_url), false) ? '1' : '0';
        }
        return $result;
    }


    /**
     * @return string
     */
    public function getStoreUidKey(): string
    {
        return $this->storeUidKey;
    }

    /**
     * @return string
     */
    public function getStoreSignKey(): string
    {
        return $this->storeSignKey;
    }
}
